# -*-coding: utf-8 -*-
# by zw on 2017/12/9


import logging
from utils.mongo import get_env_config_collection, get_app_config_collection, get_resource_config_collection
from jinja2 import Template, TemplateError
import jinja2

_logger = logging.getLogger(__name__)

import yaml as yaml_ori
from ruamel.yaml import YAML
from ruamel.yaml.compat import StringIO


class MyYAML(YAML):
    def dump(self, data, stream=None, **kw):
        inefficient = False
        if stream is None:
            inefficient = True
            stream = StringIO()
        YAML.dump(self, data, stream, **kw)
        if inefficient:
            return stream.getvalue()


myyaml = MyYAML()  # or type='safe'/'unsafe' etc
yaml = myyaml


def merge_dicts(a, b, path=None):
    "merges b into a"
    if path is None: path = []
    for key in b:
        if key in a:
            if isinstance(a[key], dict) and isinstance(b[key], dict):
                merge_dicts(a[key], b[key], path + [str(key)])
            elif a[key] == b[key]:
                pass  # same leaf value
            else:
                a[key] = b[key]
                _logger.info('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]
    return a


def get_valid_yaml(yaml_str):
    try:
        res = yaml_ori.load(yaml_str)
    except Exception as e:
        _logger.exception("yaml str %s invalid %s", yaml_str, e)
        # return {}
        raise e
    return res


def get_comment_yaml(yaml_str):
    try:
        res = yaml.load(yaml_str)
    except Exception as e:
        _logger.exception("yaml str %s invalid %s", yaml_str, e)
        return {}
    return res


async def merge_conf(doc, env):
    env_json = await get_env_json(env)
    resource = await get_resource_json()
    env_json.update(resource)
    try:
        render_str = render_from_string(doc['template'], **env_json)
    except Exception as e:
        render_str = ""
        _logger.exception(e)
        raise e

    conf_json = yaml.load(render_str) or {}

    env_str = "env_config_%s" % env
    try:
        env_conf_yaml_str = doc.get(env_str, "")
        render_str = render_from_string(env_conf_yaml_str, **env_json)
        conf_json1 = yaml.load(render_str) or {}
    except Exception as e:
        conf_json1 = doc.get(env_str, "")
        _logger.info(str(e))
        raise e

    conf = merge_dicts(conf_json, conf_json1)
    doc[env] = yaml.dump(conf, )
    return doc


async def merge_conf_loose(doc, env):
    env_json = await get_env_json(env)
    resource = await get_resource_json()
    env_json.update(resource)
    try:
        render_str = render_from_string_loose(doc['template'], **env_json)
    except Exception as e:
        render_str = ""
        _logger.exception(e)
        raise e

    conf_json = yaml.load(render_str) or {}

    env_str = "env_config_%s" % env
    try:
        env_conf_yaml_str = doc.get(env_str, "")
        render_str = render_from_string_loose(env_conf_yaml_str, **env_json)
        conf_json1 = yaml.load(render_str) or {}
    except Exception as e:
        conf_json1 = doc.get(env_str, "")
        _logger.info(str(e))
        raise e

    conf = merge_dicts(conf_json, conf_json1)
    doc[env] = yaml.dump(conf, )
    return doc


ENV = ["prod", "testenv", "dev"]


async def _render_template(template_data, env, app_name):
    doc = get_app_config_collection().find_one({"app_name": app_name}, {"_id": 0})
    _conf = await merge_conf(doc, env)
    json_data = get_valid_yaml(_conf[env])[app_name]

    env_json = await get_env_json(env)
    env_json.update(await get_resource_json())
    json_data.update(env_json)
    # template = Template(template_data)
    # render_str = template.render(json_data)
    render_str = render_from_string(template_data, **json_data)
    render_str = render_str.replace("<{", "{{")
    render_str = render_str.replace("}>", "}}")
    return render_str


async def get_env_json(env):
    cur = get_env_config_collection().find({"env": env})
    env_json = {}
    for item in cur:
        resource = await get_resource_json()
        item['yaml_str'] = render_from_string(item['yaml_str'], **resource)

        _json = get_valid_yaml(item['yaml_str'])
        env_json[item['type']] = dict(_json)
    return env_json


async def get_resource_json():
    cur = get_resource_config_collection().find()
    env_json = {}
    for item in cur:
        _json = get_valid_yaml(item['yaml_str'])
        env_json[item['type']] = dict(_json)
    return env_json


def render_from_string(template_string, **context_dict):
    env = jinja2.Environment(undefined=jinja2.StrictUndefined)
    try:
        template_obj = env.from_string(template_string)
    except jinja2.exceptions.TemplateSyntaxError as e:
        raise e
    try:
        return template_obj.render(**context_dict)
    except jinja2.exceptions.UndefinedError as e:
        raise e


from jinja2 import Undefined
import logging


class SilentUndefined(Undefined):
    '''
    Dont break pageloads because vars arent there!
    '''

    def _fail_with_undefined_error(self, *args, **kwargs):
        logging.exception('JINJA2: something was undefined!')
        return None


def render_from_string_loose(template_string, **context_dict):
    env = jinja2.Environment(undefined=SilentUndefined)
    try:
        template_obj = env.from_string(template_string)
    except jinja2.exceptions.TemplateSyntaxError as e:
        raise e
    try:
        return template_obj.render(**context_dict)
    except jinja2.exceptions.UndefinedError as e:
        raise e
